#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import time
import shutil
import socket
import pathlib
import subprocess
import lxml.html
import http.client
import urllib.parse
import urllib.error
import urllib.request
from PIL import Image
from datetime import datetime
from datetime import timedelta

import mirrors.plugin


class Main:

    def __init__(self, sock):
        self.URL = "http://www.linuxeden.com"
        self.DATE_FMT = "%Y-%m-%d"

        self.sock = sock
        if mirrors.plugin.params["run-mode"] == "init":
            self.bInit = True
        elif mirrors.plugin.params["run-mode"] == "update":
            self.bInit = False
        else:
            assert False
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.lastArticleIdFile = os.path.join(self.dataDir, ".last-article")
        self.p = InfoPrinter()

    def run(self):
        maxId = self._readLastArticleId()
        if maxId is None:
            maxId = self._getMaxArticleId()

        lastDt = None
        for i in range(maxId, 0, -1):
            self._writeLastArticleId(i)
            if i < maxId:
                time.sleep(1.0)

            url = os.path.join(self.URL, "a", str(i))
            self.p.print("Inspecting %s" % (url))
            self.p.incIndent()
            try:
                root = self._robustGetAndParseHtml(url)

                # find page date
                dt = None
                if True:
                    aElemList = root.xpath("//h1/a")
                    if len(aElemList) != 1:
                        raise BusinessException("invalid page content")
                    if aElemList[0].text is None:
                        raise BusinessException("no link")
                    m = re.fullmatch("开源美图 ([0-9]{4} [0-9]{2} [0-9]{2})", aElemList[0].text)
                    if m is None:
                        raise BusinessException("title not match")
                    try:
                        dt = datetime.strptime(m.group(1), r"%Y %m %d")
                    except ValueError:
                        if lastDt is not None:
                            dt = lastDt - timedelta(days=1)             # linuxeden.com sucks that sometimes it has wrong wallpaper date
                        else:
                            raise BusinessException("invalid date")

                # get this wallpaper
                self.p.print("wallpaper %s found" % (datetime.strftime(dt, self.DATE_FMT)))
                dstDir = os.path.join(self.dataDir, datetime.strftime(dt, self.DATE_FMT))

                # do some tests
                if os.path.isdir(dstDir):
                    if self.bInit:
                        raise BusinessException("already exists")       # for initialize, retrieve any wallpaper we don't have, jump over existing ones
                    else:
                        break                                           # for update, retrieve only new wallpapers

                # do some other tests
                if lastDt is not None and dt != lastDt - timedelta(days=1):
                    self.p.print("warning: this date is not coninuous with the last one.")
                lastDt = dt

                # find img urls
                imgElemList = root.xpath("//article/p/img")
                if len(imgElemList) != 1:
                    raise BusinessException("error: invalid page content")
                imgUrlDict = Util.parseSrcSet(imgElemList[0].get("srcset"))

                # create tmp directory
                tmpDir = os.path.join(self.dataDir, ".download")
                Util.forceMakeDir(tmpDir)

                # write ".url" file into tmpdir
                urlFile = os.path.join(tmpDir, ".url")
                with open(urlFile, "w") as f:
                    f.write(url)

                # download img files into tmpdir
                for key, imgUrl in imgUrlDict.items():
                    ext = imgUrl.split(".")[-1]

                    # download to tmp file
                    self.p.print("downloading from %s" % (imgUrl))
                    tmpFilename = os.path.join(tmpDir, ".download.%s" % (ext))
                    self._downloadFile(imgUrl, tmpFilename)

                    # rename tmpfile according to image resolution
                    with Image.open(tmpFilename) as img:
                        newFilename = os.path.join(tmpDir, "%s_%dx%d.%s" % (datetime.strftime(dt, self.DATE_FMT), img.width, img.height, ext))
                        os.rename(tmpFilename, newFilename)

                # rename tmpdir
                os.rename(tmpDir, dstDir)

                # step progress
                self.sock.progress_changed(min(maxId - i + 1, 99))
            except BusinessException as e:
                self.p.print(str(e))
            except urllib.error.HTTPError as e:
                assert e.code == 404
                self.p.print("page does not exist")
            finally:
                self.p.decIndent()

        # finished normally
        Util.forceDelete(self.lastArticleIdFile)

    def _getMaxArticleId(self):
        ret = 0
        for aElem in self._robustGetAndParseHtml(self.URL).xpath("//a"):
            m = re.fullmatch(".*/a/([0-9]+)", aElem.get("href"))
            if m is not None:
                ret = max(ret, int(m.group(1)))
        return ret

    def _readLastArticleId(self):
        if os.path.exists(self.lastArticleIdFile):
            return int(pathlib.Path(self.lastArticleIdFile).read_text())
        else:
            return None

    def _writeLastArticleId(self, articleId):
        with open(self.lastArticleIdFile, "w") as f:
            f.write(str(articleId))

    def _robustGetAndParseHtml(self, url):
        while True:
            try:
                resp = urllib.request.urlopen(url, timeout=60)
                return lxml.html.parse(resp)
            except socket.timeout as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except http.client.HTTPException as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.HTTPError as e:
                if e.code == 404:
                    raise
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.URLError as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)

    def _downloadFile(self, url, localFile):
        while True:
            try:
                subprocess.check_call(["wget", "--quiet", "--no-check-certificate", "-O", localFile, url])      # always re-dowloand
                break
            except subprocess.CalledProcessError as e:
                self.p.print("download failed and try again: %s" % str(e))
                time.sleep(60)


class BusinessException(Exception):
    pass


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMakeDir(path):
        if not os.path.exists(path):
            os.mkdir(path)
        elif not os.path.isdir(path):
            Util.forceDelete(path)
            os.mkdir(path)
        else:
            pass

    @staticmethod
    def parseSrcSet(srcSet):
        ret = {}
        for item in srcSet.split(", "):
            tlist = item.split(" ")
            ret[tlist[1]] = tlist[0]
        return ret


class InfoPrinter:

    def __init__(self):
        self.indent = 0

    def incIndent(self):
        self.indent = self.indent + 1

    def decIndent(self):
        assert self.indent > 0
        self.indent = self.indent - 1

    def print(self, s):
        line = ""
        line += "\t" * self.indent
        line += s
        print(line)


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main(sock).run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise
